#include "io.h"

static word ports [32] = {1};
static pthread_t status [32];
static void *stacks [32];
static void *(*devices [32]) (void *) = {0, tty, kbd, rnd}; 

static pthread_mutex_t io_mutex = MUTEX_INITIALIZER;
static pthread_mutex_t s_mutex = MUTEX_INITIALIZER;

word get_status (word port)
{
	return status [port];
}

word read_port (word port)
{
	word data;
	port = port & PORT_MASK;

	if (! status [port])
	{
		return 0;
	}

	// wait for the port to become available -- put process to sleep
	while (! (ports [0] & (1 << port)))
	{
		port_sleep ();
	}
	
	pthread_mutex_lock (&io_mutex);
	data = ports [port];
	ports [0] &= (~ (1 << port)) | 1; // disable the port we just read from, unless it's 0;
	pthread_mutex_unlock (&io_mutex);
	return data;
}

void write_port (word port, word data)
{
	port = port & PORT_MASK;

	if (! status [port])
	{
		return;
	}

	// wait for the port to become available -- put process to sleep
	while (! (ports [0] & (1 << port)))
	{
		port_sleep ();
	}
	
	if (port > 0)
	{
		pthread_mutex_lock (&io_mutex);
		ports [port] = data;
		ports [0] &= (~ (1 << port)) | 1; // disable the port we just wrote to, unless it's 0;
		pthread_mutex_unlock (&io_mutex);
	}
}

void start_device (word device, void *arg) //int (*device) (void *))
{
	pthread_attr_t attr;
	char *stack;
	stack = (char *) malloc (STACK_SIZE);
	stacks [device] = stack;
	pthread_attr_init (&attr);
	pthread_attr_setstack (&attr, stacks [device], STACK_SIZE);
	pthread_create (&status [device], &attr, devices [device], arg);
}

void stop_device (word device)
{
	int s;
	pthread_t tid;
	if (status [device])
	{
		tid = status [device];
		status [device] = 0;
		pthread_join (tid, 0);
		free (stacks [device]); // free its stack
	}
}

static void *kbd (void *arg)
{
	struct timeval tv;
	struct termios old;
	struct termios new;
	fd_set keyboard;
	int fd = 1;
	char c = 0;
	word w = 0;

	fd = open ("/dev/tty", O_RDONLY); // set up new terminal attributes
	tcgetattr (fd, &old);
	new = old;
	new.c_lflag &= ~ (ECHO | ECHONL | ICANON | ISIG);
	tcsetattr (fd, TCSANOW, &new);
	tcflush (fd, TCIFLUSH);

	while (w != 27 && status [KBD]) // keep on going until the KBD sees an ESC or we are asked to stop
	{
		FD_ZERO (&keyboard);    // set up the fd_set for select ()
		FD_SET (fd, &keyboard);
		tv.tv_sec = 0;
		tv.tv_usec = PORT_USEC;
		select (fd + 1, &keyboard, 0, 0, &tv); // go to sleep until we can read
		if (FD_ISSET (fd, &keyboard))
		{
			pthread_mutex_lock (&io_mutex);
			read (fd, &c, 1);
			w = c;
			ports [KBD] = w;
			ports [0] |= 1 << KBD; // enable the KBD for reading
			pthread_mutex_unlock (&io_mutex);
		}
	}

	tcsetattr (fd, TCSANOW, &old); // restore the old terminal attributes
	tcflush (fd, TCIFLUSH);

	close (fd);
	status [KBD] = 0;
	return 0;
}

static void *tty (void *arg)
{
	char c = 0;
	int fd = 0;

	fd = open ("/dev/tty", O_WRONLY);
	ports [0] |= (1 << TTY); // enable the TTY for writing

	while (status [TTY]) // go until we are asked to stop
	{
		port_sleep ();
		if (! (ports [0] & (1 << TTY)))
		{
			c = ports [TTY];
			pthread_mutex_lock (&io_mutex);
			write (fd, &c, 1);
			ports [0] |= 1 << TTY; // enable the TTY for writing
			pthread_mutex_unlock (&io_mutex);
		}
	}
	return 0;
}

static void *rnd (void *arg)
{
#ifdef RND_TELL
	double period;
	double p;
	char buf [80];
#endif // RND_TELL
	word output; 
	word V [RND_TABLE];
	const int r = LAG;
#if LAG == 1
	const dword a = 2147483583;
#elif LAG == 2
	const dword a = 2147479545;
#elif LAG == 4
	const dword a = 2147479428;
#elif LAG == 5
	const dword a = 2147472510;
#elif LAG == 8
	const dword a = 2147481504;
#elif LAG == 11
	const dword a = 2147469750;
#elif LAG == 16
	const dword a = 2147449905;
#elif LAG == 23
	const dword a = 2147473275;
#elif LAG == 32
	const dword a = 173250; // this is the minimal multiplier where a*b^r-1 and a*b^r/2-1 are prime
	                        // the period comes out around 3.6*10^303
#else
#define BAD_RND_CONFIG
	const dword a = 0; // error condition
#endif // LAG == 1
	dword c = 1;
	dword x;
#ifdef SHUFFLE
	dword j; // for shuffle
#endif // SHUFFLE
	int i, k, l;
#ifdef RND_TELL
#ifdef BAD_RND_CONFIG
	tty_puts ("***rnd: invalid configuration \n");
#else
	p = (double) a * pow ((double) 0x80000000, (double) r) - 1.0;
	period = (p - 1.0) / 2.0; // this is a lower bound. could be p - 1.
	sprintf (buf, "rnd: a:%u b:2e+31 r:%u period:~%.3le", (int) a, (int) r, period);
#ifdef SHUFFLE
	sprintf (buf + strlen (buf), " +shuffle %d \n", RND_TABLE);
#else
	sprintf (buf + strlen (buf), " -shuffle \n");
#endif // SHUFFLE
	tty_puts (buf);
#endif // BAD_RND_CONFIG
#endif // RND_TELL
	x = *(word *) arg;
	x = 2147483583 * x + c; // since lag is 1 during initialization, use an appropriate multiplier
	c = x >> 31;
	V [0] = x & RND_MAX;
	for (i = 0, k = 1; i < 32 || i < RND_TABLE; i ++)      // initialize the table 
	{                                                      // we make several complete passes
		l = k - 1;                                           // to thoroughly mix the state
		if (l < 0) l += RND_TABLE;
		x = 2147483583 * V [l] + c; // since lag is 1 during initialization, use an appropriate multiplier
		c = x >> 31;
		V [k] = x & RND_MAX;
		k = (k + 1) % RND_TABLE;
	}
	
	while (status [RND]) // go until we are asked to stop
	{
		port_sleep ();
		if (! (ports [0] & (1 << RND)))
		{
			l = k - r;
			if (l < 0) l += RND_TABLE;
			
			x = a * V [l] + c;
			c = x >> 31;
			x = x & RND_MAX;
#ifdef SHUFFLE
			j = x * RND_TABLE;
			j = j / (RND_MAX + 1);
			output = V [j];
			V [j] = x;
#else
			V [k] = x;
			output = x;
#endif // SHUFFLE
			k = (k + 1) % RND_TABLE;

			pthread_mutex_lock (&io_mutex);
			ports [RND] = output;
			ports [0] |= 1 << RND; // enable the RND for reading
			pthread_mutex_unlock (&io_mutex);
		}
	}
	return 0;
}

void tty_putc (word c)
{
	write_port (TTY, c & 0xFF);
}

void tty_puts (char *s)
{
	word w;
	pthread_mutex_lock (&s_mutex);
	while (*s)
	{
		w = (word) *s;
		tty_putc (w);
		s ++;
	}
	pthread_mutex_unlock (&s_mutex);
}

word kbd_getc (void)
{
	return read_port (KBD);
}

static void port_sleep (void)
{
#ifdef NANOSLEEP
	struct timespec ts;
	ts.tv_sec = 0;
	ts.tv_nsec = PORT_USEC * 1000;
	nanosleep (&ts, 0);
#else
	usleep (PORT_USEC);
#endif
}

